﻿
using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.InteropServices;
using BGSpaceShooter;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
  
namespace BGSpaceShooterServer
{
    /// <summary>
    /// Pakiet przesyłany przez sieć od klienta do serwera.
    /// </summary>
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct ClientToServerPacket
    {

        /// <summary>
        /// Typ wiadomości
        /// </summary>
        private byte messageType;

        /// <summary>
        /// Typ gracza: 0 - Player, 1 - Spectator || Identyfikator gracza
        /// </summary>
        private ushort playerTypeOrID;

        /// <summary>
        /// Int kodów wciśniętych przycisków.
        /// </summary>
        private int keysPressed;

        /// <summary>
        /// Typ pojazdu: 0 - Viper
        /// </summary>
        private ushort fighterType;

        /// <summary>
        /// Czas nadania wiadomości.
        /// </summary>
        public DateTime TimeStamp;


        /// <summary>
        /// Lista wciśniętych przycisków
        /// </summary>
        public List<Keys> KeysPressed
        {
            get
            {
                List<Keys> keys = new List<Keys>();
                for (int i=0; i < sizeof(int)*8; i++)
                {
                    int temp = keysPressed >> i;
                    if ((temp & 1) == 1)
                    {
                        Keys key;
                        switch (i)
                        {
                            case 0:
                                key = Keys.Left;
                                break;
                            case 1:
                                key = Keys.Right;
                                break;
                            case 2:
                                key = Keys.Up;
                                break;
                            case 3:
                                key = Keys.Down;
                                break;
                            case 4:
                                key = Keys.W;
                                break;
                            case 5:
                                key = Keys.S;
                                break;
                            case 6:
                                key = Keys.Space;
                                break;
                            case 7:
                                key = Keys.A;
                                break;
                            case 8:
                                key = Keys.D;
                                break;
                            case 9:
                                key = Keys.U;
                                break;
                            case 10:
                                key = Keys.P;
                                break;
                            default:
                                key = Keys.Space;
                                break;
                        }
                        keys.Add(key);
                        
                    }
                }
                return keys;
            }

            set
            {
                int result = 0; 
                foreach (Keys key in value)
                {
                    switch (key)
                    {
                        case Keys.Left:
                            result |= 1;
                            break;
                        case Keys.Right:
                            result |= 2;
                            break;
                        case Keys.Up:
                            result |= 4;
                            break;
                        case Keys.Down:
                            result |= 8;
                            break;
                        case Keys.W:
                            result |= 16;
                            break;
                        case Keys.S:
                            result |= 32;
                            break;
                        case Keys.Space:
                            result |= 64;
                            break;
                        case Keys.A:
                            result |= 128;
                            break;
                        case Keys.D:
                            result |= 256;
                            break;
                        case Keys.U:
                            result |= 512;
                            break;
                        case Keys.P:
                            result |= 1024;
                            break;
                    }
                }
                keysPressed = result;
            }
        }

        /// <summary>
        /// Typ wiadomości
        /// </summary>
        public MessageType MessageType
        {
            get
            {
                return (MessageType)messageType;
            }
            set
            {
                messageType = (byte)value;
            }
        }

        /// <summary>
        /// Typ gracza: Player | Spectator. To pole może być używane tylko przy wiadomości typu RequestJoin
        /// </summary>
        public UserType PlayerType
        {
            get
            {
                if (MessageType != MessageType.RequestJoin)
                {
                    throw new ArgumentException("To pole może być używane tylko przy wiadomości typu RequestJoin");
                }
                return (UserType)playerTypeOrID;
            }
            set
            {
                if (MessageType != MessageType.RequestJoin)
                {
                    throw new ArgumentException("To pole może być używane tylko przy wiadomości typu RequestJoin");
                }
                playerTypeOrID = (ushort)value;
            }
        }

        /// <summary>
        /// Identyfikator gracza. To pole nie może być używane przy wiadomości typu RequestJoin
        /// </summary>
        public ushort PlayerID
        {
            get
            {
                if (MessageType == MessageType.RequestJoin)
                {
                    throw new ArgumentException("To pole nie może być używane przy wiadomości typu RequestJoin");
                }
                return playerTypeOrID;
            }
            set
            {
                if (MessageType == MessageType.RequestJoin)
                {
                    throw new ArgumentException("To pole nie może być używane przy wiadomości typu RequestJoin");
                }
                playerTypeOrID = (ushort)value;
            }
        }

        /// <summary>
        /// Typ pojazdu. To pole może być używane tylko przy wiadomości typu RequestJoin
        /// </summary>
        public GameObjectName FighterType
        {
            get
            {
                if (MessageType != MessageType.RequestJoin)
                {
                    throw new ArgumentException("To pole może być używane tylko przy wiadomości typu RequestJoin");
                }
                return (GameObjectName)fighterType;
            }
            set
            {
                if (MessageType != MessageType.RequestJoin)
                {
                    throw new ArgumentException("To pole może być używane tylko przy wiadomości typu RequestJoin");
                }
                fighterType = (ushort)value;
            }
        }

        /// <summary>
        /// Wiadomość wysyłana na czat
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 200)]
        public string Message;
    }

    internal class CTSEventArgs : EventArgs
    {
        public ClientToServerPacket ClientToServerPacket { get; private set; }
        public IPEndPoint Sender { get; private set; }
        public CTSEventArgs(ClientToServerPacket clientToServerPacket, IPEndPoint sender)
        {
            ClientToServerPacket = clientToServerPacket;
            Sender = sender;
        }
    }

    internal delegate void CTSEventHandler(object sender, CTSEventArgs eventArgs);

    /// <summary>
    /// Pakiet przesyłany przez sieć z serwera do klienta. Zawiera zakapsułkowaną strukturę typu ServerStatusResponse
    /// </summary>
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct ServerToClientPacket
    {
        /// <summary>
        /// Typ wiadomości.
        /// </summary>
        private byte messageType;
        
        /// <summary>
        /// Podtyp wiadomości
        /// </summary>
        private byte messageSubType;

        private ServerStatusResponse serverStatusResponse;
        
        /// <summary>
        /// Typ wiadomości
        /// </summary>
        public MessageType MessageType
        {
            get
            {
                return (MessageType)messageType;
            }
            set
            {
                messageType = (byte)value;
            }
        }

        /// <summary>
        /// ID gracza, którego dotyczy wiadomość
        /// </summary>
        public int PlayerID
        {
            get
            {
                return serverStatusResponse.playerId;
            }
            set
            {
                serverStatusResponse.playerId = value;
            }
        }

        public ServerStatusResponse ServerStatusResponse
        {
            get
            {
                return serverStatusResponse;
            }
            set
            {
                serverStatusResponse = value;
            }
        }

        /// <summary>
        /// Typ zdarzenia. Może być używany tylko przez wiadomości typu Event.
        /// </summary>
        public EventType EventType
        {
            get
            {
                if (MessageType != MessageType.Event)
                {
                    throw new ArgumentException("To pole może być używane tylko przy wiadomości typu Event");
                }
                return (EventType) messageSubType;
            }
            set
            {
                if (MessageType != MessageType.Event)
                {
                    throw new ArgumentException("To pole może być używane tylko przy wiadomości typu Event");
                }
                messageSubType = (byte) value;
            }
        }
    }
}